package com.sxj.shop.util;

import com.sxj.shop.exception.DataAccessException;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 石小俊
 * @date 2023年12月2023/12/7日 18:04
 * 模板方法:对JDBC访问数据库的操作进行封装
 */
public class JDBCTemplate<T> {

    /**
     * 执行一个DML操作。即:insert、update、delete
     * 对于所有的DML操作,使用的都是ps.executeUpdate()方法执行sql
     * 对于整个方法而言,不同的地方主要有两块:1.sql不同 2.sql的参数不同
     * 将这两个不同的部分封装成模板方法的参数
     * 而由于sql的参数不确定,可能没有,也可能只有一个,还可能有多个
     * 因此,可以使用数组或者可变长参数来进行封装
     *
     * @param sql
     * @param params
     */
    public void update(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JDBCUtil.getConnection();
            ps = conn.prepareStatement(sql);
            // 设置参数
            // 在模板方法中无法确定参数是什么
            // 我们通过可变长来进行接收
            // 因此,可以遍历接收到的可变长的数据,根据可变长参数的数量进行参数的设置
            // 可变长参数中有几个元素,就设置即可参数的值
            for (int i = 0; i < params.length; i++) {
                // 取出当前遍历的元素
                Object param = params[i];
//                if(param instanceof String){
//                    ps.setString(i+1,(String)param);
//                }
                // 由于参数类型过多,可以直接使用Object来表示
                ps.setObject(i + 1, param);
            }
            // 执行DML操作
            ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException("数据访问异常");
        } finally {
            JDBCUtil.close(ps, null);
        }
    }

    /**
     * 执行一个查询操作
     * 查询操作的结果返回的是结果集,结果集的数据可能是一条,也可能是多条
     * 可以将结果集转换成一个Java对象或者Java对象集合
     * 由于此时无法确定调用者需要映射的对象是谁,因此将映射方法封装成接口方法
     * 由方法调用者对接口方法做具体的实现
     *
     * @param sql
     * @param rm
     * @param params
     * @return
     */
    public List<T> query(String sql, RowMapper<T> rm, Object... params) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<T> list = new ArrayList<>();
        try {
            conn = JDBCUtil.getConnection();
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }
            rs = ps.executeQuery();
            while (rs.next()) {
                // 此时调用行映射接口实现映射
                T t = rm.mapRow(rs);
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException("数据访问异常");
        } finally {
            JDBCUtil.close(ps, rs);
        }
    }

    /**
     * 执行DML操作,用于保存数据的同时返回数据库自动生成键的值
     * @param psc
     * @param keyHolder
     */
    public void update(PreparedStatementCreator psc, KeyHolder keyHolder) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtil.getConnection();
            // 调用接口方法获取对应的状态集
            // 需要哪一种状态集,由模板方法的调用者在调用时做具体的实现
            ps = psc.createPreparedStatement(conn);

            // 执行DML操作
            ps.executeUpdate();

            // 当执行了DML操作之后,数据库中会自动生成对应的主键
            rs = ps.getGeneratedKeys();

            // 准备一个集合,用于存储所有自动生成的键的值
            List<Object> list = new ArrayList<>();

            // 通过结果集元数据获取生成的主键的值
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                for (int i = 1; i <= columnCount; i++) {
                    list.add(rs.getObject(i));
                }
            }

            // 将返回的主键值存储到参数keyHolder中
            keyHolder.setKeyList(list);
        } catch (Exception e) {
            e.printStackTrace();
            throw new DataAccessException("数据访问异常");
        } finally {
            JDBCUtil.close(ps, null);
        }
    }
}
